package bancosys.tec.services.junitx;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.commons.lang.builder.CompareToBuilder;

import bancosys.tec.services.IntegrationTestCase;

/**
 * @author Rafael Volpato (Sep 30, 2007)
 */
public class IntegrationTestSuite extends TestSuite {

    private final String CENARIOS = "/cenarios/";

    private final String NOT_EXECUTED = "Testes não executados";

    /**
     * Construtor vazio (não faz nada).
     */
    public IntegrationTestSuite() {
        // empty constructor
    }

    public <T extends IntegrationTestCase> IntegrationTestSuite(Class<T> clazz) {
        this.setName(clazz.getName());

        Constructor<T> constructor;
        Constructor<T> constructorURL;
        T testCase;

        if (!IntegrationTestCase.class.isAssignableFrom(clazz)) {
            this.addTest(warning("Class " + clazz.getName() + " does not extend the superclass IntegrationTestCase"));
            return;
        }

        try {
            constructor = clazz.getConstructor(new Class[0]);
            constructorURL = clazz.getConstructor(new Class[]{ URL.class });
        } catch (NoSuchMethodException e) {
            this.addTest(warning("Class " + clazz.getName() + " has no public constructor TestCase(URL) or TestCase()"));
            return;
        }

        if (!Modifier.isPublic(clazz.getModifiers())) {
            this.addTest(warning("Class " + clazz.getName() + " is not public"));
            return;
        }

        try {
            testCase = constructor.newInstance(new Object[0]);
            this.createTestCases(constructorURL, testCase);

        } catch (Exception e) {
            e.printStackTrace();
            this.addTest(warning("Could not create new instance of " + clazz.getName() + ", " + e.getMessage()));
            return;
        }

        if (this.testCount() == 0) {
            this.addTest(warning("No tests found in " + clazz.getName()));
        }
    }

    protected <T extends IntegrationTestCase> void createTestCases(Constructor<T> constructorURL, T testCase) throws IOException,
            InstantiationException, IllegalAccessException, InvocationTargetException {
        Map<String, List<URL>> indexedTestSuites = this.indexTestsByFolder(testCase.getFilesToExecute());
        List<String> testSuites = new ArrayList<String>(indexedTestSuites.keySet());
        Collections.sort(testSuites);
        for (String testSuiteName : testSuites) {
            TestSuite testSuite = new TestSuite(testSuiteName);
            TestSuite testSuiteNExec = new TestSuite(this.NOT_EXECUTED);

            for (URL url : indexedTestSuites.get(testSuiteName)) {
                if (this.NOT_EXECUTED.equals(testSuiteName)) {
                    testSuiteNExec.addTest(doNotExecuteTest(url.getPath()));
                } else {
                    testSuite.addTest(constructorURL.newInstance(url));
                }
            }
            this.addTest(testSuite);
            if (testSuiteNExec.countTestCases() > 0) {
                this.addTest(testSuiteNExec);
            }
        }
    }

    /**
     * Agrupa os arquivos de teste por diretorio.
     * 
     * @param inputFiles arquivos de teste que vão ser executados
     * @return mapa aonde a chave é um diretorio, e o valor uma lista de arquivos
     */
    protected Map<String, List<URL>> indexTestsByFolder(List<URL> inputFiles) {
        Map<String, List<URL>> indexedTestSuites = new HashMap<String, List<URL>>();
        Collections.sort(inputFiles, new Comparator<URL>() {
            public int compare(URL o1, URL o2) {
                return new CompareToBuilder().append(o1.getFile(), o2.getFile()).toComparison();
            }
        });

        for (URL url : inputFiles) {
            String folder = null;
            if (url.getPath().indexOf("/NExec-") != -1) {
                folder = this.NOT_EXECUTED;
            } else {
                if (url.getPath().indexOf(this.CENARIOS) != -1) {
                    folder = url.getPath().substring(url.getPath().indexOf(this.CENARIOS), url.getPath().lastIndexOf("/"));
                } else {
                    folder = url.getPath().substring(0, url.getPath().lastIndexOf("/"));
                }
            }

            List<URL> testCases = indexedTestSuites.get(folder);
            if (testCases == null) {
                testCases = new ArrayList<URL>();
                indexedTestSuites.put(folder, testCases);
            }

            testCases.add(url);
        }

        return indexedTestSuites;
    }

    /**
     * Returns a test which will fail and log a warning message.
     */
    public static Test warning(final String message) {
        return new TestCase("warning") {
            @Override
            protected void runTest() {
                fail(message);
            }
        };
    }

    /**
     * Returns a test which will fail and log an error message.
     * 
     * @param message test case name
     * @return test case.
     */
    private static Test doNotExecuteTest(final String message) {
        return new TestCase(message) {
            @Override
            protected void runTest() {
                fail("Esse teste foi configurado para não ser executado!");
            }
        };
    }
}
